﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Core;
using System.Drawing;
using Silvermoon.OpenGL.Native;
using Silvermoon.Windows;

namespace Silvermoon.Shapes
{
    /// <summary>
    /// Rectangle shape with either landscape or portrait gradient with multiple gradient stops
    /// Each gradient stop can be either fixed in size (StopType.Fixed) or relative to the actual width/height of the shape in relation to the offset.
    /// </summary>
    public class GradientStopShape : Shape
    {
        #region fields

        private GradientStop[] gradientStops;
        private int start, end;
        private Orientation orientation;
        short[] vertexArray;
        int[] colorArray;
        int lastAlpha;
        private int lastHeight;

        #endregion
        #region ctor

        public GradientStopShape()
            : base()
        {
        }

        public GradientStopShape(int start, int end, params GradientStop[] stops):base()
        {
            this.end = end;
            this.start = start;
            this.gradientStops = stops;
        }

        #endregion
        #region props

        /// <summary>
        /// Specifies whether the gradient appears in landscape mode (left to right) or in portrait mode (top to bottom).
        /// </summary>
        public Orientation Orientation
        {
            get { return orientation; }
            set
            {
                if (orientation != value)
                {
                    orientation = value;
                    InvalidateArrays();
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the start offset for relative gradient stops. 
        /// Each <see cref="GradientStop"/> should have an offset value which is between <see cref="P:Start/"> and <see cref="P:End"/>.
        /// </summary>
        public int Start
        {
            get { return start; }
            set
            {
                if (start != value)
                {
                    start = value;
                    InvalidateArrays();
                }
            }
        }

        /// <summary>
        /// Gets or sets the end offset for relative gradient stop.
        /// Each <see cref="GradientStop"/> should have an offset value which is between <see cref="P:Start/"> and <see cref="P:End"/>.
        /// </summary>
        public int End
        {
            get { return end; }
            set
            {
                if (end != value)
                {
                    end = value;
                    InvalidateArrays();
                }
            }
        }

        /// <summary>
        /// Gets or sets the gradient stops.
        /// Each gradient stop can be either fixed in size (StopType.Fixed) or relative to the actual width/height of the shape in relation to the offset.
        /// See also <seealso cref="P:Start"/> and <seealso cref="P:End"/> properties.
        /// </summary>
        public GradientStop[] GradientStops
        {
            get { return gradientStops; }
            set
            {
                if (gradientStops != value)
                {
                    gradientStops = value;
                    InvalidateArrays();
                }
            }
        }
        #endregion
        #region methods

        protected override void Layout()
        {
            base.Layout();
            vertexArray = null;
            colorArray = null;
        }

        private void InvalidateArrays()
        {
            vertexArray = null;
            colorArray = null;
            Invalidate();
        }


        public void Update()
        {
            InvalidateArrays();
        }



        public override void Render(Renderer renderer)
        {
            if (gradientStops == null) return;

            int alpha = (renderer.Alpha * Opacity) / 255;
            if (lastAlpha != alpha) colorArray = null;
            if (lastHeight != Height) vertexArray = null;

            if (colorArray == null) colorArray = GetColorArray(alpha);
            if (vertexArray == null) vertexArray = GetVertexArray();

            lastAlpha = alpha;
            lastHeight = Height;

            gl.Disable(Capability.Texture2D);
            gl.DisableClientState(ClientStateArray.TextureCoord);
            gl.EnableClientState(ClientStateArray.Color);
            gl.ColorPointer(4, glValueType.UnsignedByte, 0, colorArray);
            gl.CheckError();
            gl.VertexPointer(2, glValueType.Short, 0, vertexArray);
            gl.CheckError();
            gl.DrawArrays(PrimitiveType.TriangleStrip, 0, vertexArray.Length / 2);
            gl.CheckError();
            gl.EnableClientState(ClientStateArray.TextureCoord);
            gl.CheckError();
            gl.Enable(Capability.Texture2D);
            gl.MatrixMode(MatrixMode.ModelView);

        }

        private short[] GetVertexArray()
        {
            return Orientation == Orientation.Landscape ? GetLandscapeVertexArray() : GetPortraitVertexArray();
        }      

        private unsafe short[] GetLandscapeVertexArray()
        {
            short h = (short)Height;
            int w = Width;
            int range = (this.end - this.start);

            GradientStop[] stops = ValidateStops(this.gradientStops);

            foreach (GradientStop stop in stops)
            {
                if (stop.Type == StopType.Absolute) w -= stop.Offset;
            }
            w = Math.Max(0, w);

            short[] array = new short[stops.Length * 4];

            int abs = 0;
            int index = 0;
            short x = 0;

            foreach (GradientStop stop in stops)
            {
                short x1;
                if (stop.Type == StopType.Absolute)
                {
                    abs += stop.Offset;
                    x1 = (short)(x + stop.Offset);
                }
                else
                {
                    x1 = (short)(w * stop.Offset / range + abs);
                }
                fixed (short* ptr = &array[index])
                {
                    ptr[0] = x1; ptr[1] = h;
                    ptr[2] = x1; ptr[3] = 0;
                }
                x = x1;
                index += 4;
            }
            return array;
        }

        private unsafe int[] GetColorArray(int alpha)
        {
            GradientStop[] stops = ValidateStops(this.gradientStops);

            int[] array = new int[stops.Length * 2];

            int c = GetAlphaBlendColor(stops[0].Color, alpha);
            int index = 0;

            fixed (int* ptr = &array[0])
            {
                foreach (GradientStop stop in stops)
                {
                    c = GetAlphaBlendColor(stop.Color, alpha);
                    ptr[index++] = c;
                    ptr[index++] = c;
                }
            }
            return array;
        }

        private unsafe short[] GetPortraitVertexArray()
        {
            short w = (short)Width;
            int h = Height;
            int range = (this.end - this.start);

            GradientStop[] stops = ValidateStops(this.gradientStops);

            foreach (GradientStop stop in stops)
            {
                if (stop.Type == StopType.Absolute) h -= stop.Offset;
            }
            h = Math.Max(0, h);

            short[] array = new short[stops.Length * 4];

            int abs = 0;
            int index = 0;
            short y = 0;

            foreach (GradientStop stop in stops)
            {
                short y1;
                if (stop.Type == StopType.Absolute)
                {
                    abs += stop.Offset;
                    y1 = (short)(y + stop.Offset);
                }
                else
                {
                    y1 = (short)(h * stop.Offset / range + abs);
                }
                fixed (short* ptr = &array[index])
                {
                    ptr[0] = w; ptr[1] = y1;
                    ptr[2] = 0; ptr[3] = y1;
                }
                y = y1;
                index += 4;
            }
            return array;
        }

        unsafe private static GradientStop[] ValidateStops(GradientStop[] stops)
        {
            if (stops.Length == 1)
            {
                GradientStop stop = stops[0];
                stops = new GradientStop[] { stop, stop };
            }
            return stops;
        }

        private int GetAlphaBlendColor(GlColor color, int alpha)
        {
            color.A = (byte)(color.A * alpha / 255);
            return color.Value;
        }

        protected override Shape CreateClone()
        {
            return new GradientStopShape();
        }

        protected override void Copy(Shape shape)
        {
            base.Copy(shape);
            GradientStopShape s = shape as GradientStopShape;
            s.Orientation = Orientation;
            s.Start = Start;
            s.End = End;
            s.GradientStops = GradientStops;
        }

        #endregion
    }

}
